/***********************************************************************
 *
 * $CVSHeader$
 *
 * This file is part of WebScarab, an Open Web Application Security
 * Project utility. For details, please see http://www.owasp.org/
 *
 * Copyright (c) 2002 - 2004 Rogan Dawes
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Getting Source
 * ==============
 *
 * Source for this application is maintained at Sourceforge.net, a
 * repository for free software projects.
 *
 * For details, please see http://www.sourceforge.net/projects/owasp
 *
 */

/*
 * SpiderPanel.java
 *
 * Created on August 6, 2003, 10:01 PM
 */

package org.owasp.webscarab.plugin.spider.swing;

import org.owasp.webscarab.model.HttpUrl;

import org.owasp.webscarab.plugin.spider.Spider;
import org.owasp.webscarab.plugin.spider.SpiderModel;
import org.owasp.webscarab.plugin.spider.SpiderUI;
import org.owasp.webscarab.plugin.spider.Link;

import org.owasp.webscarab.ui.swing.HeaderPanel;
import org.owasp.webscarab.ui.swing.UrlTreeRenderer;
import org.owasp.webscarab.ui.swing.UrlTreeModelAdapter;
import org.owasp.webscarab.ui.swing.SwingPluginUI;
import org.owasp.webscarab.util.swing.ColumnDataModel;

import javax.swing.tree.TreePath;
import javax.swing.tree.TreeModel;
import javax.swing.Action;
import javax.swing.AbstractAction;
import javax.swing.SwingUtilities;
import javax.swing.JOptionPane;

import java.util.logging.Logger;

/**
 *
 * @author  rdawes
 */
public class SpiderPanel extends javax.swing.JPanel implements SwingPluginUI, SpiderUI {
    
    /**
	 * 
	 */
	private static final long serialVersionUID = 8563871484142539698L;
	private SpiderModel _model;
    private Spider _spider;
    
    private Logger _logger = Logger.getLogger(this.getClass().getName());
    
    private HeaderPanel _hp;
    
    /** Creates new form SpiderPanel */
    public SpiderPanel(Spider spider) {
        initComponents();
        _hp  = new HeaderPanel();
        _hp.setEditable(true);
        
        _spider = spider;
        _model = _spider.getModel();
        
        TreeModel treeModel = new UrlTreeModelAdapter(_model.getUrlModel());
        unseenLinkTree.setModel(treeModel);
        unseenLinkTree.setRootVisible(false);
        unseenLinkTree.setShowsRootHandles(true);
        unseenLinkTree.setCellRenderer(new UrlTreeRenderer());
        
        configure();
    }
    
    public javax.swing.JPanel getPanel() {
        return this;
    }
    
    public String getPluginName() {
        return new String("Spider");
    }
    
    private void configure() {
        recursiveCheckBox.setSelected(_model.getRecursive());
        cookieSyncCheckBox.setSelected(_model.getCookieSync());
        domainRegexTextField.setText(_model.getAllowedDomains());
        pathRegexTextField.setText(_model.getForbiddenPaths());
    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    private void initComponents() {//GEN-BEGIN:initComponents
        java.awt.GridBagConstraints gridBagConstraints;

        jLabel1 = new javax.swing.JLabel();
        domainRegexTextField = new javax.swing.JTextField();
        jLabel2 = new javax.swing.JLabel();
        pathRegexTextField = new javax.swing.JTextField();
        recursiveCheckBox = new javax.swing.JCheckBox();
        stopButton = new javax.swing.JButton();
        cookieSyncCheckBox = new javax.swing.JCheckBox();
        linkTreePanel = new javax.swing.JPanel();
        jScrollPane2 = new javax.swing.JScrollPane();
        unseenLinkTree = new javax.swing.JTree();
        linkTreeFetchSelectionButton = new javax.swing.JButton();
        linkTreeFetchTreeButton = new javax.swing.JButton();
        headerButton = new javax.swing.JButton();

        setLayout(new java.awt.GridBagLayout());

        jLabel1.setText("Allowed Domains");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        add(jLabel1, gridBagConstraints);

        domainRegexTextField.setToolTipText("A regular expression describing which domains to include");
        domainRegexTextField.setMinimumSize(new java.awt.Dimension(60, 19));
        domainRegexTextField.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                domainRegexTextFieldActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        add(domainRegexTextField, gridBagConstraints);

        jLabel2.setText("Forbidden Paths");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        add(jLabel2, gridBagConstraints);

        pathRegexTextField.setToolTipText("A regular expression describing which paths to exclude");
        pathRegexTextField.setMinimumSize(new java.awt.Dimension(60, 19));
        pathRegexTextField.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                pathRegexTextFieldActionPerformed(evt);
            }
        });
        pathRegexTextField.addFocusListener(new java.awt.event.FocusAdapter() {
            public void focusLost(java.awt.event.FocusEvent evt) {
                pathRegexTextFieldFocusLost(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 3;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        add(pathRegexTextField, gridBagConstraints);

        recursiveCheckBox.setText("Fetch Recursively");
        recursiveCheckBox.setToolTipText("Enables recursive fetching of Links");
        recursiveCheckBox.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING);
        recursiveCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                recursiveCheckBoxActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        add(recursiveCheckBox, gridBagConstraints);

        stopButton.setText("Stop");
        stopButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                stopButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 3;
        add(stopButton, gridBagConstraints);

        cookieSyncCheckBox.setText("Synchronise cookies");
        cookieSyncCheckBox.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING);
        cookieSyncCheckBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cookieSyncCheckBoxActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        add(cookieSyncCheckBox, gridBagConstraints);

        linkTreePanel.setLayout(new java.awt.GridBagLayout());

        jScrollPane2.setViewportView(unseenLinkTree);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        linkTreePanel.add(jScrollPane2, gridBagConstraints);

        linkTreeFetchSelectionButton.setText("Fetch Selection");
        linkTreeFetchSelectionButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                linkTreeFetchSelectionButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.weightx = 1.0;
        linkTreePanel.add(linkTreeFetchSelectionButton, gridBagConstraints);

        linkTreeFetchTreeButton.setText("Fetch Tree");
        linkTreeFetchTreeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                linkTreeFetchTreeButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.weightx = 1.0;
        linkTreePanel.add(linkTreeFetchTreeButton, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        add(linkTreePanel, gridBagConstraints);

        headerButton.setText("Headers");
        headerButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                headerButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 3;
        add(headerButton, gridBagConstraints);

    }//GEN-END:initComponents

    private void headerButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_headerButtonActionPerformed
        _hp.setHeaders(_model.getExtraHeaders());
        JOptionPane.showMessageDialog(this, _hp, "Spider Extra Headers", JOptionPane.PLAIN_MESSAGE);
        _model.setExtraHeaders(_hp.getHeaders());
    }//GEN-LAST:event_headerButtonActionPerformed
    
    private void cookieSyncCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cookieSyncCheckBoxActionPerformed
        _model.setCookieSync(cookieSyncCheckBox.isSelected());
    }//GEN-LAST:event_cookieSyncCheckBoxActionPerformed
    
    private void linkTreeFetchTreeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_linkTreeFetchTreeButtonActionPerformed
        TreePath[] selection = unseenLinkTree.getSelectionPaths();
        if (selection != null && selection.length==1) {
            if (_spider.isBusy()) {
                _logger.warning("Spider is still busy, please wait");
                JOptionPane.showMessageDialog(SpiderPanel.this, "Spider is still busy, please wait");
                return;
            }
            HttpUrl url = (HttpUrl) selection[0].getLastPathComponent();
            _spider.requestLinksUnder(url);
        } else {
            _logger.info("Cannot fetch a tree if there are 0 or many paths selected!");
            JOptionPane.showMessageDialog(SpiderPanel.this, "Cannot fetch a tree if there are 0 or many paths selected!");
        }
    }//GEN-LAST:event_linkTreeFetchTreeButtonActionPerformed
    
    private void linkTreeFetchSelectionButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_linkTreeFetchSelectionButtonActionPerformed
        TreePath[] selection = unseenLinkTree.getSelectionPaths();
        if (selection == null || selection.length == 0) return;
        if (_spider.isBusy()) {
            _logger.warning("Spider is still busy, please wait");
            // FIXME show a message
            return;
        }
        unseenLinkTree.clearSelection();
        HttpUrl[] urls = new HttpUrl[selection.length];
        for (int i=0; i<selection.length; i++) {
            urls[i] = (HttpUrl) selection[i].getLastPathComponent();
        }
        _spider.requestLinks(urls);
    }//GEN-LAST:event_linkTreeFetchSelectionButtonActionPerformed
    
    private void pathRegexTextFieldFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_pathRegexTextFieldFocusLost
        //_model.setForbiddenPaths(pathRegexTextField.getText());
    }//GEN-LAST:event_pathRegexTextFieldFocusLost
    
    private void pathRegexTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pathRegexTextFieldActionPerformed
        _model.setForbiddenPaths(pathRegexTextField.getText());
    }//GEN-LAST:event_pathRegexTextFieldActionPerformed
    
    private void domainRegexTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_domainRegexTextFieldActionPerformed
        _model.setAllowedDomains(domainRegexTextField.getText());
    }//GEN-LAST:event_domainRegexTextFieldActionPerformed
    
    private void stopButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_stopButtonActionPerformed
        _spider.clearQueue();
    }//GEN-LAST:event_stopButtonActionPerformed
    
    private void recursiveCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_recursiveCheckBoxActionPerformed
        _model.setRecursive(recursiveCheckBox.isSelected());
    }//GEN-LAST:event_recursiveCheckBoxActionPerformed
    
    public Action[] getConversationActions() {
        return null;
    }
    
    public Action[] getUrlActions() {
        return new Action[] {new SpiderAction()};
    }
    
    public void linkDequeued(final Link link, final int queueSize) {
        if (SwingUtilities.isEventDispatchThread()) {
            if (link != null) {
                // _logger.info(queueSize + " in queue, fetching " + link.getURL());
            } else {
                // _logger.info("Cleared queue");
            }
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    linkDequeued(link, queueSize);
                }
            });
        }
    }
    
    public void linkQueued(final Link link, final int queueSize) {
        if (SwingUtilities.isEventDispatchThread()) {
            _logger.fine(queueSize + " in queue, adding: " + link.getURL());
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    linkQueued(link, queueSize);
                }
            });
        }
    }
    
    public void setEnabled(final boolean enabled) {
        if (SwingUtilities.isEventDispatchThread()) {
            cookieSyncCheckBox.setEnabled(enabled);
            domainRegexTextField.setEnabled(enabled);
            linkTreeFetchSelectionButton.setEnabled(enabled);
            linkTreeFetchTreeButton.setEnabled(enabled);
            pathRegexTextField.setEnabled(enabled);
            recursiveCheckBox.setEnabled(enabled);
            stopButton.setEnabled(enabled);
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    setEnabled(enabled);
                }
            });
        }
    }
    
    public ColumnDataModel[] getConversationColumns() {
        return null;
    }
    
    public ColumnDataModel[] getUrlColumns() {
        return null;
    }
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JCheckBox cookieSyncCheckBox;
    private javax.swing.JTextField domainRegexTextField;
    private javax.swing.JButton headerButton;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JButton linkTreeFetchSelectionButton;
    private javax.swing.JButton linkTreeFetchTreeButton;
    private javax.swing.JPanel linkTreePanel;
    private javax.swing.JTextField pathRegexTextField;
    private javax.swing.JCheckBox recursiveCheckBox;
    private javax.swing.JButton stopButton;
    private javax.swing.JTree unseenLinkTree;
    // End of variables declaration//GEN-END:variables
    
    private class SpiderAction extends AbstractAction {
        
        /**
		 * 
		 */
		private static final long serialVersionUID = -2207351321290083769L;

		public SpiderAction() {
            putValue(NAME, "Spider tree");
            putValue(SHORT_DESCRIPTION, "Fetches any unseen links below this point");
            putValue("URL", null);
        }
        
        public void actionPerformed(java.awt.event.ActionEvent e) {
            Object o = getValue("URL");
            if (o == null || !(o instanceof HttpUrl)) return;
            HttpUrl url = (HttpUrl) o;
            if (! _spider.isBusy()) {
                _spider.requestLinksUnder(url);
            } else {
                _logger.warning("Spider is still busy, please wait");
                JOptionPane.showMessageDialog(SpiderPanel.this, "Spider is still busy, please wait");
            }
        }
        
        public void putValue(String key, Object value) {
            super.putValue(key, value);
            if (key != null && key.equals("URL")) {
                if (value != null && value instanceof HttpUrl) {
                    this.setEnabled(true);
                } else {
                    this.setEnabled(false);
                }
            }
        }
        
    }
}
